package com.zh.mycommon.util;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Random;

/**
 * 生成随机验证码
 *
 * @author ZH
 * @date 12:44 2020/4/15
 */
public final class RandomCodeUtil {
    //ascii码[48-57]表示[0-9], [65-90]表示[A-Z], [97,122]表示[a,z]
    //大小和小写字母的转换
    private static final int delta = 32;

    //不含容易和字母混淆的0,1
    private static final char[] number_chars = {'2', '3', '4', '5', '6', '7', '8', '9'};
    //不含容易和数字混淆的i、l、o、z
    private static final char[] letter_chars = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y'};

    private static final char[] special_chars = {'!', '@', '#', '$', '%', '?', '&', '+', '='};

    static final Random random = new Random();

    /**
     * 随机生成固定位数的int数
     * 注意：第一位永远不会是0
     *
     * @param digit 长度，不能超出1-10范围
     * @return 随机整数 <b>示例：27842773</b>
     */
    public static long randomInt(int digit) {
        if (digit < 1 || digit >10) {
            throw new IllegalArgumentException("digit不能<1 or >10");
        }
        //Math.random()*10 会导致长度可能会变短,所以使用(1 + Math.random() * 9)替代
        return (long) ((1 + Math.random() * 9) * Math.pow(10, digit - 1));
    }

    /**
     * 生成固定长度的密码。默认参数
     *
     * @param len 生成密码的长度  建议至少6位
     * @return 生成的密码
     */
    public static String getPasswordCode(int len) {
        return randomCharacterCode(Math.max(len, 6), 4);
    }

    /**
     * 随机生成只含‘数字’的验证码
     *
     * @param len 字符长度，不能超出2-10范围
     * @return 随机数字的字符串 <b>示例：'27842773'</b>
     */
    public static String getNumberCode(int len) {
        if (len > 10 || len < 2)
            throw new IllegalArgumentException("len 不符合2-10的长度要求");

        NumberFormat format = new DecimalFormat("#" + "0".repeat(len));
        return format.format(Math.random() * Math.pow(10, len));
    }

    /**
     * 随机生成包含‘数字+字母’的验证码
     *
     * @param len          生成的字符长度，至少6位
     * @param hasLowerChar 是否混合小写字母。若false，则不含有小写字母
     * @return 随机的字符串
     */
    public static String getCharacterCode(int len, boolean hasLowerChar) {
        return randomCharacterCode(Math.max(len, 6), hasLowerChar ? 3 : 1);
    }


    /**
     * 随机生成指定长度的字符串。自定义参数
     *
     * @param len  生成字符串的长度  建议至少6位
     * @param type 1-数字+大写字母、2-数字+小写字母、3-数字+大小写混合、4-数字+大小写混合+特殊字符
     * @return 生成的随机字符串
     */
    private static String randomCharacterCode(int len, int type) {
        if (len < 6)
            throw new IllegalArgumentException("len 不能<6");
        if (type < 1 || type > 4)
            throw new IllegalArgumentException("type 值非法");

        var numbersLength = number_chars.length * 2; //此处×2是为了增大‘数字’的概率
        var range = numbersLength + letter_chars.length;

        char[] arr = new char[len];
        for (int i = 0; i < len; i++) {
            int index = random.nextInt(range);

            if (index < numbersLength) { //10   0-9;  20 0-9
                arr[i] = number_chars[index % number_chars.length];
            } else {
                var addition = switch (type) {
                    //大写
                    case 1 -> 0;
                    //小写
                    case 2 -> delta;
                    //大小写混合
                    case 3, 4 ->
                        //random是偶数，则小写。此处bound=3是为了增大‘小写字符’的概率
                            (random.nextInt(3) % 2 == 0) ? delta : 0;
                    default -> throw new IllegalStateException("Unexpected value: " + type);
                };
                arr[i] = (char) (letter_chars[index - numbersLength] + addition);
            }
        }

        //把普通字符随机替换为特殊字符
        if (type == 4) {
            var specialCount = (len - 1) / 4; //随机字符的概率
            var step = len / (specialCount + 1);

            for (int i = 0; i < specialCount; i++) {
                var index = step * (i + 1) + i + random.nextInt(-1, 2); //加一个干扰，移动[-1,0,1]
                arr[index % len] = special_chars[random.nextInt(special_chars.length)];
            }
        }
        return String.valueOf(arr);
    }

}
